#include "spl.includes.h"
#include "splib.h"

using namespace SPLib;
using namespace SPLib::Settings;
using namespace SPLib::Geom;

#define MAX_INT_LENGTH      15
#define MAX_LONG_LENGTH     15
#define MAX_DOUBLE_LENGTH   30

FileSettings::FileSettings(PCTSTR szLocation)
    : SettingsBase(szLocation), pBuffer(NULL), lWriteTime(0), hFile(NULL)
{
}

FileSettings::~FileSettings()
{
    // delete file buffer
    delete[] pBuffer;
    // close file handle
    CloseHandle(hFile);
}

BOOL FileSettings::init()
{
    // open/create settings file
    hFile = CreateFile(getLocation(), GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
        OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    // if file is opened, read settings file
    if (hFile != INVALID_HANDLE_VALUE)
        readSettings();

    // return operation state (TRUE: success, FALSE: failure)
    return hFile != INVALID_HANDLE_VALUE;
}

void FileSettings::readSettings()
{
    // settings file attributes
    WIN32_FILE_ATTRIBUTE_DATA fileAttr;

    // get settings file attributes
    GetFileAttributesEx(getLocation(), GetFileExInfoStandard, (PVOID)&fileAttr);
    // calculate write time
    ULONGLONG lWriteTime =
        ((ULONGLONG)fileAttr.ftLastWriteTime.dwHighDateTime << 32) +
        ((ULONGLONG)fileAttr.ftLastWriteTime.dwLowDateTime);

    // if file buffer up to date, exit function
    if (this->lWriteTime == lWriteTime) return;

    // set file write time
    this->lWriteTime = lWriteTime;
    // set file buffer size
    dwBufferSize = fileAttr.nFileSizeLow / sizeof(TCHAR) + 1;

    // delete previous file buffer
    delete[] pBuffer;
    // allocate file buffer
    pBuffer = new TCHAR[dwBufferSize];

    // number of bytes read from settings file
    DWORD dwBytesRead;

    // read entire settings file
    if (ReadFile(hFile, (LPVOID)pBuffer,
        fileAttr.nFileSizeLow, &dwBytesRead, NULL)) {
        // set null terminating character for security
        pBuffer[dwBufferSize - 1] = 0;
    }

    // if file not completely read,
    // set buffer size to 0 to indicate an error occured
    if (dwBytesRead != fileAttr.nFileSizeLow) dwBufferSize = 0;
}

BOOL FileSettings::getOffset(PCTSTR szName, PTCHAR *pStartChar, PTCHAR *pEndChar)
{
    // start at beginning of buffer
    PTCHAR pStart = pBuffer;
    // value name length
    size_t nLength = _tcslen(szName);

    // while value name found
    while (pStart = _tcsstr(pStart, szName)) {
        // if value name is at beginning of line and ends with '=',
        // value name is valid
        if ((pStart - 1 == pBuffer || *(pStart - 1) == '\n') &&
            *(pStart + nLength) == _T('=')) {
            // set value start pointer
            pStart += nLength;
            break;
        }

        // set value start to last line end
        pStart = _tcschr(pStart, _T('\r'));
    }

    // if value name not found, abort
    if (!pStart) return FALSE;

    // find value associated with the given name
    // if value not found, abort
    if (!(pStart = _tcschr(pStart, _T('=')))) return FALSE;

    // find end of line
    PTCHAR pEnd = _tcschr(pStart, _T('\r'));

    // if end of line not found, set last character as value end
    if (!pEnd) pEnd = pBuffer + dwBufferSize - 1;

    // set value start and end chars
    *pStartChar = pStart + 1;
    *pEndChar = pEnd;

    // value found, operation successfull
    return TRUE;
}

BOOL FileSettings::getSetting(PCTSTR szName, PTSTR *value, DWORD *dwSize)
{
    // read settings file
    readSettings();
    // if file buffer error, abort
    if (!dwBufferSize) return FALSE;

    // value start and end
    TCHAR *pStart, *pEnd;

    // get value start and end, abort if not found
    if (!getOffset(szName, &pStart, &pEnd)) return FALSE;

    // calculate value length and create value buffer
    *value = new TCHAR[(*dwSize = (DWORD)(pEnd - pStart)) + 1];

    // copy value to value buffer
    memcpy((PVOID)*value, (PVOID)pStart, *dwSize * sizeof(TCHAR));
    // append null terminating character
    (*value)[*dwSize] = 0;

    // operation successfull
    return TRUE;
}

BOOL FileSettings::getSetting(PCTSTR szName, PBYTE bMod, PBYTE bVK)
{
    // read settings file
    readSettings();
    // if file buffer error, abort
    if (!dwBufferSize) return FALSE;

    // setting value start and end
    TCHAR *pStart, *pEnd;

    // get hotkey (format: MODIFIER,[CHAR|KEYCODE])
    if (getOffset(_T("hotkey"), &pStart, &pEnd)) {
        // last processed character
        TCHAR *pLast;

        // if hotkey specified
        if (!compareParam(pStart, _T("NONE"), &pLast)) {
            // find hotkey modifier
            if (compareParam(pStart, _T("WIN"), &pLast)) {
                // windows key modifier
                *bMod = MOD_WIN;
            } else if (compareParam(pStart, _T("CTRL"), &pLast)) {
                // control key modifier
                *bMod = MOD_CONTROL;
            } else if (compareParam(pStart, _T("ALT"), &pLast)) {
                // alt key modifier
                *bMod = MOD_ALT;
            } else if (compareParam(pStart, _T("SHIFT"), &pLast)) {
                // shift key modifier
                *bMod = MOD_SHIFT;
            } else {
                // error, abort operation
                return FALSE;
            }

            // if one character left
            if (++pLast + 1 == pEnd && (
                (*pLast >= '0' && *pLast <= '9') ||
                (*pLast >= 'A' && *pLast <= 'Z'))) {
                // set character as VK code
                *bVK = (BYTE)*pLast;
            } else {
                // convert decimal VK code
                *bVK = (BYTE)_tcstol(pLast, &pLast, 10);
            }
        }
    } else {
        // error, abort operation
        return FALSE;
    }

    // operation successfull
    return TRUE;
}

BOOL FileSettings::getSetting(PCTSTR szName, Size __out &value)
{
    // read settings file
    readSettings();
    // if file buffer error, abort
    if (!dwBufferSize) return FALSE;

    // value start and end
    TCHAR *pStart, *pEnd;

    // get cstring dimentions (format: width,height)
    if (getOffset(szName, &pStart, &pEnd)) {
        // convert and store window width and height
        value.cx = _tcstoul(pStart, &pStart, 10);
        value.cy = _tcstoul(++pStart, &pStart, 10);
    } else {
        // error, abort operation
        return FALSE;
    }

    // operation successfull
    return TRUE;
}

BOOL FileSettings::getSetting(PCTSTR szName, Bounds __out &value)
{
    // read settings file
    readSettings();
    // if file buffer error, abort
    if (!dwBufferSize) return FALSE;

    // value start and end
    TCHAR *pStart, *pEnd;

    // get cstring bounds (format: x,y,width,height)
    if (getOffset(szName, &pStart, &pEnd)) {
        // convert cstring numeric values to long values (x, y, cx, cy)
        // and store them into bounds coordinates array
        for (int j = 0; j < 4; ++j) {
            value.coords[j] = _tcstol(pStart, &pStart, 10);
            ++pStart;
        }
    } else {
        // error, abort operation
        return FALSE;
    }

    // operation successfull
    return TRUE;
}

BOOL FileSettings::getSetting(PCTSTR szName, LONG __out *value)
{
    // read settings file
    readSettings();
    // if file buffer error, abort
    if (!dwBufferSize) return FALSE;

    // value start and end
    TCHAR *pStart, *pEnd;

    // get value start and end, abort if not found
    if (!getOffset(szName, &pStart, &pEnd)) return FALSE;

    // convert cstring value to long value
    *value = _tcstol(pStart, &pEnd, 16);

    // operation successfull
    return TRUE;
}

BOOL FileSettings::getSetting(PCTSTR szName, INT __out *value)
{
    // read settings file
    readSettings();
    // if file buffer error, abort
    if (!dwBufferSize) return FALSE;

    // value start and end
    TCHAR *pStart, *pEnd;

    // get value start and end, abort if not found
    if (!getOffset(szName, &pStart, &pEnd)) return FALSE;

    // convert cstring value to long value
    *value = (INT)_tcstol(pStart, &pEnd, 16);

    // operation successfull
    return TRUE;
}

BOOL FileSettings::getSetting(PCTSTR szName, DOUBLE __out *value)
{
    // read settings file
    readSettings();
    // if file buffer error, abort
    if (!dwBufferSize) return FALSE;

    // value start and end
    TCHAR *pStart, *pEnd;

    // get value start and end, abort if not found
    if (!getOffset(szName, &pStart, &pEnd)) return FALSE;

    // convert cstring value to long value
    *value = _tcstod(pStart, &pEnd);

    // operation successfull
    return TRUE;
}

BOOL FileSettings::setSetting(PCTSTR szName, PCTSTR value, DWORD dwLength)
{
    // value start and end
    TCHAR *pStart, *pEnd;
    // number of bytes written
    DWORD dwBytesWritten;

    // read settings file
    readSettings();

    // find value start and end
    if (getOffset(szName, &pStart, &pEnd)) {
        // close file handle
        CloseHandle(hFile);
        // recreate settings file
        hFile = CreateFile(getLocation(), GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        // reset file write time
        lWriteTime = 0;

        // write chars before value
        WriteFile(hFile, (PVOID)pBuffer,
            (DWORD)(pStart - pBuffer) * (DWORD)sizeof(TCHAR),
            &dwBytesWritten, NULL);

        // write value
        WriteFile(hFile, (PVOID)value,
            dwLength * (DWORD)sizeof(TCHAR), &dwBytesWritten, NULL);

        // write chars after value
        WriteFile(hFile, (PVOID)pEnd,
            (dwBufferSize - (DWORD)(pEnd - pBuffer) - 1) * (DWORD)sizeof(TCHAR),
            &dwBytesWritten, NULL);
    } else {
        // length of line to append
        DWORD dwLineLength = 3 + (DWORD)_tcslen(szName) + dwLength;
        // line to append
        PTCHAR pszLine = new TCHAR[dwLineLength];
        // last copied character
        PTCHAR pLast = pszLine;

        // append carriage return
        *(pLast++) = '\r';
        // append line feed
        *(pLast++) = '\n';
        // append value name
        pLast = copyString(pLast, szName);
        // append separator
        *(pLast++) = '=';
        // append value
        memcpy((PVOID)pLast, (const PVOID)value, dwLength * (DWORD)sizeof(TCHAR));

        // write line
        WriteFile(hFile, (PVOID)pszLine,
            dwLineLength * sizeof(TCHAR), &dwBytesWritten, NULL);

        // delete line
        delete[] pszLine;
    }

    // reset file pointer
    SetFilePointer(hFile, 0, 0, FILE_BEGIN);

    // operation successfull
    return TRUE;
}

BOOL FileSettings::setSetting(PCTSTR szName, BYTE __in bMod, BYTE __in bVK)
{
    // hotkey cstring value
    TCHAR szValue[12];
    // pointer to last copied character
    PTCHAR pLast;

    // append hotkey modifier to cstring value
    switch (bMod) {
        case MOD_WIN:
            // windows key modifier
            pLast = copyString(szValue, _T("WIN"));
            break;
        case MOD_CONTROL:
            // control key modifier
            pLast = copyString(szValue, _T("CTRL"));
            break;
        case MOD_ALT:
            // alt key modifier
            pLast = copyString(szValue, _T("ALT"));
            break;
        case MOD_SHIFT:
            // shift key modifier
            pLast = copyString(szValue, _T("SHIFT"));
            break;
        default:
            // invalid modifier, abort operation
            return FALSE;
    }

    // append separator
    *(pLast++) = ',';

    // append hotkey virtual keycode
    if ((bVK >= '0' && bVK <= '9') || (bVK >= 'A' && bVK <= 'Z')) {
        // append keycode as a character
        *pLast = bVK;
    } else {
        // append keycode as an decimal integer value
        _itot(bVK, pLast, 10);
    }

    // save cstring value
    return setSetting(szName, szValue, (DWORD)(pLast + 1 - szValue));
}

BOOL FileSettings::setSetting(PCTSTR szName, Size __in value)
{
    // cstring value
    TCHAR szValue[MAX_LONG_LENGTH * 2 + 1];

    // appennd cstring converted width
    PTCHAR pLast = _ltot(value.cx, szValue, 10);
    // append separator
    *(++pLast) = ',';
    // append cstring converted height
    pLast = _ltot(value.cy, ++pLast, 10);

    // save cstring value
    return setSetting(szName, szValue, (DWORD)(pLast + 1 - szValue));
}

BOOL FileSettings::setSetting(PCTSTR szName, Bounds __in value)
{
    // cstring value
    TCHAR szValue[MAX_LONG_LENGTH * 4 + 3];
    // last converted character
    PTCHAR pLast = szValue;
    // coordinate index
    INT i = 0;

    // while there are coordinates to convert
    while (1) {
        // append cstring converted coordinate
        pLast = _ltot(value.coords[i], pLast, 10) + 1;

        // if not last coordinate
        if (++i < 4) {
            // append separator
            *(pLast++) = ',';
        } else {
            // conversion done, break
            break;
        }
    }

    // save cstring value
    return setSetting(szName, szValue, (DWORD)(pLast - szValue));
}

BOOL FileSettings::setSetting(PCTSTR szName, LONG __in value)
{
    // cstring value
    TCHAR szValue[MAX_LONG_LENGTH + 1];

    // convert long value into cstring
    PTCHAR pLast = _ltot(value, szValue, 10);

    // save setting
    return setSetting(szName, szValue, (DWORD)(pLast + 1 - szValue));
}

BOOL FileSettings::setSetting(PCTSTR szName, INT __in value)
{
    // cstring value
    TCHAR szValue[MAX_INT_LENGTH + 1];

    // convert integer value into cstring
    PTCHAR pLast = _itot(value, szValue, 10);

    // save setting
    return setSetting(szName, szValue, (DWORD)(pLast + 1 - szValue));
}

BOOL FileSettings::setSetting(PCTSTR szName, DOUBLE __in value)
{
    // cstring value
    TCHAR szValue[MAX_DOUBLE_LENGTH + 1];

    // save cstring converted value
    return setSetting(szName, szValue, _stprintf(szValue, _T("%.15f"), value));
}